﻿using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;

namespace Ipmce.RemoteVoting.Common.Tools.CardReader.ImageParsing
{
	abstract class LdsTagElement : IElement
	{
		private readonly byte[] _tag;
		private readonly bool _longSize;
		private readonly bool _optional;
		private int? _fixedSize;
		protected List<IElement> Children;

		#region Writing

		protected virtual int WriteTag(byte[] data, int offset)
		{
			Array.Copy(_tag, 0, data, offset, _tag.Length);
			return offset + _tag.Length;
		}

		protected virtual int WriteChildrenSize(byte[] data, int offset)
		{
			if (_longSize)
			{
				data[offset] = BioInfoTags.SizePrefix;
				offset += 1;
				Array.Copy(
					BigEndianConverter.GetByteArray(
						GetChildrenSize(), 2),
					0, data, offset, 2);
				offset += 2;
			}
			else
			{
				data[offset] = (byte)GetChildrenSize();
				offset += 1;
			}
			return offset;
		}

		protected virtual int WriteChildren(byte[] data, int offset)
		{
			return Children.Aggregate(offset, (current, child) => child.Write(data, current));
		}

		public int Write(byte[] data, int offset)
		{
			offset = WriteTag(data, offset);
			offset = WriteChildrenSize(data, offset);
			offset = WriteChildren(data, offset);
			return offset;
		}

		#endregion

		#region Parsing

		protected virtual int ParseTag(byte[] data, int offset)
		{
			if (_tag.Where((t, i) => t != data[offset + i]).Any())
			{
				return offset;
			}
			return offset + _tag.Length;
		}

		protected virtual int ParseChildrenSize(byte[] data, int offset, ref int childrenSize)
		{
			if (_longSize)
			{
				if (data[offset] != BioInfoTags.SizePrefix)
					throw new InvalidDataException();
				offset += 1;
				childrenSize = BigEndianConverter.GetInt(
					new[] { data[offset], data[offset + 1] });
				offset += 2;
			}
			else
			{
				childrenSize = data[offset];
				offset += 1;
			}
			if (_fixedSize.HasValue && childrenSize != _fixedSize)
				throw new InvalidDataException();

			return offset;
		}

		protected virtual int ParseChildren(byte[] data, int offset)
		{
			return Children.Aggregate(offset, (current, child) => child.Parse(data, current));
		}

		public int Parse(byte[] data, int offset)
		{
			int newOffset = ParseTag(data, offset);
			if (newOffset == offset)
				if (_optional)
					return offset;
				else
					throw new InvalidDataException();
			offset = newOffset;

			int childrenSize = 0;
			offset = ParseChildrenSize(data, offset, ref childrenSize);
			if (_fixedSize.HasValue && _fixedSize.Value != childrenSize)
				throw new InvalidDataException();

			newOffset = ParseChildren(data, offset);
			if (newOffset - offset != childrenSize)
				throw new InvalidDataException();
			offset = newOffset;

			return offset;
		}

		#endregion

		#region Size

		protected virtual int GetChildrenSize()
		{
			return Children.Sum(p => p.GetSize());
		}

		public int GetSize()
		{
			return _tag.Length + (_longSize ? 3 : 1) + GetChildrenSize();
		}

		#endregion

		protected LdsTagElement(byte[] tag, bool longSize, int? fixedSize, bool optional)
		{
			_tag = tag;
			_longSize = longSize;
			_fixedSize = fixedSize;
			_optional = optional;
			Children = new List<IElement>();
		}
	}
}